package com.whfc.ms.shiro;

import com.google.common.cache.LoadingCache;
import com.whfc.common.enums.EnableState;
import com.whfc.common.enums.SysUserType;
import com.whfc.common.util.DateUtil;
import com.whfc.common.util.JSONUtil;
import com.whfc.common.util.SessionAttr;
import com.whfc.entity.dto.login.ThirdLoginToken;
import com.whfc.fuum.dto.WxUserToken;
import com.whfc.fuum.entity.SysUser;
import com.whfc.fuum.entity.WxUser;
import com.whfc.fuum.service.SysUserService;
import com.whfc.ms.api.sys.service.MsLoginService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Set;

/**
 * 登录策略
 *
 * @author xuguocheng
 */
public class MsUserRealm extends AuthorizingRealm {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @DubboReference(interfaceClass = SysUserService.class, version = "1.0.0")
    private SysUserService sysUserService;

    @Autowired
    private MsLoginService loginService;

    @Resource(name = "userRulesCache")
    private LoadingCache<Integer, Set<String>> userRulesCache;


    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 获取当前已登录的用户
        Object primaryPrincipal = principals.getPrimaryPrincipal();
        if (primaryPrincipal instanceof SysUser) {
            SysUser user = (SysUser) primaryPrincipal;
            Set<String> permissions = null;
            try {
                permissions = userRulesCache.get(user.getId());
            } catch (Exception ex) {
                logger.error("获取用户权限错误.", ex);
            }
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            if (permissions != null) {
                info.addStringPermissions(permissions);
            }
            return info;
        }
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
        logger.debug("doGetAuthenticationInfo,token:{}", JSONUtil.toString(authcToken));
        //账号密码认证
        if (authcToken instanceof UsernamePasswordToken) {
            UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
            return userPassLogin(token);
        }
        //微信网站用户认证
        else if (authcToken instanceof WxUserToken) {
            WxUserToken token = (WxUserToken) authcToken;
            return wxLogin(token);
        }
        // 第三方平台登录
        else if (authcToken instanceof ThirdLoginToken) {
            ThirdLoginToken token = (ThirdLoginToken) authcToken;
            return thirdLogin(token);
        }

        return null;
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken
                || token instanceof WxUserToken
                || token instanceof ThirdLoginToken;
}

    /**
     * 账号密码登录
     *
     * @param token
     * @return
     */
    private AuthenticationInfo userPassLogin(UsernamePasswordToken token) {
        Session session = SecurityUtils.getSubject().getSession();
        String username = token.getUsername();
        // 根据登陆名account从库中查询user对象
        SysUser sysUser = sysUserService.getUserByUsername(username);
        if (sysUser == null) {
            throw new AuthenticationException("账号或密码错误");
        }
        if (EnableState.DISABLED.getValue().equals(sysUser.getStatus())) {
            throw new AuthenticationException("账号已被禁用");
        }
        //检查用户是否是体验用户,如果是的话，体验时间是否到期
        if (SysUserType.EXPERIENCE.getValue().equals(sysUser.getType())) {
            Date expireTime = sysUser.getExpireTime();
            if (expireTime == null) {
                throw new AuthenticationException("账号已过期");
            }

            expireTime = DateUtil.getDateBegin(expireTime);
            Date today = DateUtil.getDateBegin(new Date());
            if (expireTime.before(today)) {
                throw new AuthenticationException("账号已过期");
            }
        }

        //清空缓存
        this.cleanUserRulesCache(sysUser.getId());

        session.setAttribute(SessionAttr.MS_USER, sysUser);
        session.setAttribute(SessionAttr.LOGIN_METHOD, SessionAttr.USERPASS_LOGIN);

        String password = new SimpleHash("MD5", token.getPassword(), sysUser.getSalt()).toString();
        token.setPassword(password.toCharArray());
        return new SimpleAuthenticationInfo(sysUser, sysUser.getPassword(), this.getName());
    }

    /**
     * 微信授权登录
     *
     * @param token
     * @return
     */
    private AuthenticationInfo wxLogin(WxUserToken token) {
        Session session = SecurityUtils.getSubject().getSession();
        WxUser wxUser = token.getWxUser();
        //session保存微信用户
        session.setAttribute(SessionAttr.WXWEB_USER, wxUser);

        //session保存系统用户
        String phone = wxUser.getPhone();
        if (StringUtils.isEmpty(phone)) {
            throw new AuthenticationException("未绑定手机号");
        }
        SysUser sysUser = sysUserService.getUserByPhone(phone);
        if (sysUser == null) {
            throw new AuthenticationException("系统用户不存在");
        }

        //清空缓存
        this.cleanUserRulesCache(sysUser.getId());

        session.setAttribute(SessionAttr.MS_USER, sysUser);
        session.setAttribute(SessionAttr.LOGIN_METHOD, SessionAttr.WX_LOGIN);
        return new SimpleAuthenticationInfo(sysUser, wxUser.getOpenId(), this.getName());
    }

    /**
     * 第三方登录
     *
     * @param thirdLoginToken
     * @return
     */
    private AuthenticationInfo thirdLogin(ThirdLoginToken thirdLoginToken) {
        Session session = SecurityUtils.getSubject().getSession();
        String code = thirdLoginToken.getCode();
        SysUser sysUser = sysUserService.getUserByCode(code);
        if (sysUser == null) {
            throw new AuthenticationException("系统用户不存在");
        }

        //清空缓存
        this.cleanUserRulesCache(sysUser.getId());

        session.setAttribute(SessionAttr.MS_USER, sysUser);
        session.setAttribute(SessionAttr.LOGIN_METHOD, SessionAttr.THIRD_LOGIN);
        return new SimpleAuthenticationInfo(sysUser, code, this.getName());
    }

    /**
     * 清理本地用户权限缓存
     *
     * @param userId
     */
    private void cleanUserRulesCache(Integer userId) {
        userRulesCache.refresh(userId);
    }

}